package top.kikt.book.core.base

import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.openqa.selenium.chrome.ChromeOptions
import org.openqa.selenium.support.ui.WebDriverWait
import top.kikt.book.util.HttpUtils
import java.io.File
import java.net.Proxy
import java.nio.charset.Charset
import java.time.Duration

/**
 * 读取图书的帮助类
 */
interface BookHandler {

    fun createBookMetaHandler(): BookMetaHandler

    fun createBookChapterHandler(): BookChapterHandler

    fun forceCharset(): Charset?

    var charset: Charset

    fun headers(): Map<String, String> = emptyMap()

    fun proxy(): Proxy? = null

    fun useChromium(): Boolean = false

    fun get(url: String): Response {
        val proxy = proxy()
        if (proxy != null) {
            return HttpUtils.get(url, headers(), proxy)
        }
        return HttpUtils.get(url, headers())
    }

    /**
     * 复写这个方法，可以使用chromium来获取配置中对应的网页
     */
    fun getUrlDocument(url: String): Document? {
        val bytes = get(url).body?.bytes() ?: return null

        // guest charset
        val charset = forceCharset() ?: guestCharset(bytes)
        this.charset = charset

        val tocHtml = bytes.toString(charset)

        val output = File("output/tmp")

        val httpUrl = url.toHttpUrl()
        val host = httpUrl.host
        val path = httpUrl.pathSegments.joinToString("_")

        val dir = File(output, host)
        val pathFile = File(dir, path)
        pathFile.parentFile.mkdirs()
        pathFile.writeText(tocHtml)
        println("write to ${pathFile.absolutePath}")
        return Jsoup.parse(tocHtml)
    }

    fun guestCharset(bytes: ByteArray): Charset {
        // 1. 转为 string, 强行使用 utf-8
        val str = bytes.toString(Charsets.UTF_8)
        // 2. 获取 meta charset
        val doc = Jsoup.parse(str)
        val charset = doc.selectFirst("meta[charset]")?.attr("charset")
        if (charset != null) {
            return Charset.forName(charset)
        }
        // 3. 获取 meta content-type
        val contentType = doc.selectFirst("meta[http-equiv=Content-Type]")?.attr("content")
        @Suppress("DuplicatedCode")
        if (contentType != null) {
            val c = contentType.split(";").find { it.contains("charset") }?.split("=")?.get(1)
            if (c != null) {
                return Charset.forName(c)
            }
        }
        // 4. 获取 html charset
        val charset2 = doc.selectFirst("html")?.attr("charset")
        if (charset2 != null) {
            return Charset.forName(charset2)
        }
        // 5. 获取 html content-type
        val contentType2 = doc.selectFirst("html")?.attr("content")
        @Suppress("DuplicatedCode")
        if (contentType2 != null) {
            val c = contentType2.split(";").find { it.contains("charset") }?.split("=")?.get(1)
            if (c != null) {
                return Charset.forName(c)
            }
        }
        // 6. 默认使用 utf-8
        return Charsets.UTF_8
    }

    /**
     * 复写这个方法，可以使用chromium来获取 menu 对应的网页
     */
    fun getMenuDocument(url: String): Document? {
        val tocHtml = get(url).body?.bytes()?.toString(charset) ?: return null
        return Jsoup.parse(tocHtml)
    }

    /**
     * 这个方法是为了解决一些网站需要js渲染的问题
     */
    fun chromiumGet(
        url: String,
        waitDuration: Duration = Duration.ofSeconds(2),
        waitUntil: (webDriver: WebDriver) -> Unit
    ): Document? {
        val options = ChromeOptions()
        val driver: WebDriver = ChromeDriver(options)
        return try {
            driver.get(url)
            WebDriverWait(driver, waitDuration).until(waitUntil)
            Jsoup.parse(driver.pageSource)
        } catch (e: Exception) {
            e.printStackTrace()
            null
        } finally {
            driver.close()
        }
    }

    fun String.toDocument(): Document? {
        return Jsoup.parse(this)
    }

    fun HttpUrl.toDocument(): Document? {
        HttpUtils.get(this).let {
            return it.body?.bytes()?.toString(charset)?.toDocument()
        }
    }


    fun HttpUrl.postForm(formData: Map<String, String>): Response {
        return HttpUtils.postForm(this, formData, headers())
    }

}